#pragma once

#define ALT_ESC_QUIT

#include "wlroots.hpp"
#include "wayland-glib.h"
#include "utils.h"
#include "macros.h"

#ifdef ALT_ESC_QUIT
#include <xkbcommon/xkbcommon.h>
#endif

#include <list>

class OutputWrapper;
class ToplevelWrapper;
class CursorWrapper;
class KeyboardWrapper;
class SeatWrapper;
class Workspace;
class View;
class XdgView;

class Server
{
    friend class OutputWrapper;
    friend class ToplevelWrapper;
    friend class CursorWrapper;
    friend class SeatWrapper;
    friend class Workspace;
    friend class View;
public:
    Server();
    ~Server();
    void run(const char *startup_cmd = nullptr);

    inline struct wl_display* wl_display() { return m_display; }
    // inline struct wl_backend* wl_backend() { return m_backend; }

    OutputWrapper* output_nearest_to_cursor();

private:
    // void server_quit(Server* server);
    static gboolean handle_sigterm(gpointer user_data);

    void new_output(struct wlr_output *wlr_output);
    void output_layout_change();

    void output_init();
    void add_output_to_layout(OutputWrapper *output);

    void xdg_shell_init();
    void new_xdg_surface(struct wlr_xdg_surface *xdg_surface);

    void input_init();
    void new_input(struct wlr_input_device* device);
    void new_pointer(struct wlr_input_device *device);
    void new_keyboard(struct wlr_input_device *device);

    void workspaces_init();

    ToplevelWrapper *desktop_toplevel_at(double lx, double ly,
                                         struct wlr_surface **surface, double *sx, double *sy);

    View *view_at(double lx, double ly,
                  struct wlr_surface **surface, double *sx, double *sy);
    bool _view_at(View* view, double lx, double ly,
                  struct wlr_surface **surface, double *sx, double *sy);

    void on_cursor_motion(double x, double y, uint32_t time);
    void on_cursor_button(double x, double y, struct wlr_pointer_button_event* event);
    void on_cursor_axis(struct wlr_pointer_axis_event *event);
    void reset_cursor_mode();
    void on_cursor_frame();
    void on_cursor_move(int x, int y);
    void on_cursor_resize(uint32_t time);
    void on_request_cursor(struct wlr_surface* surface, int hotspot_x, int hotspot_y);
    void on_modifiers(struct wlr_keyboard* keyboard);
    void on_key(struct wlr_keyboard* keyboard, struct wlr_keyboard_key_event* event);

#ifdef ALT_ESC_QUIT
    bool handle_keybinding(xkb_keysym_t sym);
#endif
    void focus_toplevel(ToplevelWrapper *toplevel, struct wlr_surface *surface);
    void toplevel_unmap(ToplevelWrapper *toplevel);
    void toplevel_request_move(XdgView* view);
    void toplevel_request_resize(XdgView* view, wlr_edges edges);
    void toplevel_request_maximize(ToplevelWrapper *toplevel);
    void toplevel_request_fullscreen(ToplevelWrapper *toplevel);
    void begin_interactive(XdgView *view, enum cursor_mode mode, enum wlr_edges edges);

    OutputWrapper* output_from_wlr_output(struct wlr_output *wlr_output);
    OutputWrapper* output_nearest_to(int x, int y);

private:
    struct wl_display *m_display;
    GMainLoop* m_glib_loop;

    struct wlr_backend *m_backend;
    struct wlr_session *m_session;
    struct wlr_renderer *m_renderer;
    struct wlr_allocator *m_alloc;

    struct wlr_scene *m_scene;
    struct wlr_scene_output_layout *m_scene_layout;

    /*
     * While an output layout change is in process, this counter is
     * non-zero and causes change-events from the wlr_output_layout
     * to be ignored (to prevent, for example, moving views in a
     * transitory layout state).  Once the counter reaches zero,
     * do_output_layout_change() must be called explicitly.
     */
    int m_pending_output_layout_change;

    struct wlr_gamma_control_manager_v1 *m_gamma_control_manager_v1;
    wl_listener_wrapper m_new_output;
    struct Output* m_output;
    std::list<OutputWrapper*> m_outputs;
    struct wlr_output_layout *m_output_layout;
    wl_listener_wrapper m_layout_change;
    struct wlr_output_manager_v1 *m_output_manager;

    struct wlr_xdg_shell *m_xdg_shell;
    wl_listener_wrapper m_new_xdg_surface;
    struct wlr_xdg_activation_v1 *m_xdg_activation;
    std::list<ToplevelWrapper*> m_toplevels;

    ToplevelWrapper *m_grabbed_toplevel = nullptr;
    View *m_grabbed_view = nullptr;
    // double m_grab_x;
    // double m_grab_y;
    // struct wlr_box m_grab_box;
    // uint32_t m_resize_edges;

    struct wlr_compositor *m_compositor;

    CursorWrapper *m_cursor;

    wl_listener_wrapper m_new_input;
    SeatWrapper *m_seat;
    std::list<KeyboardWrapper*> m_keyboards;

    /* Tree for all non-layer xdg/xwayland-shell surfaces */
    struct wlr_scene_tree *m_view_tree;
    /*
     * Popups need to be rendered above always-on-top views, so we reparent
     * them to this dedicated tree
     */
    struct wlr_scene_tree *m_xdg_popup_tree;

    /* Tree for all non-layer xdg/xwayland-shell surfaces with always-on-top/below */
    struct wlr_scene_tree *m_view_tree_always_on_top;
    struct wlr_scene_tree *m_view_tree_always_on_bottom;
#ifdef HAVE_XWAYLAND
    /* Tree for unmanaged xsurfaces without initialized view (usually popups) */
    struct wlr_scene_tree *m_unmanaged_tree;
#endif
    /* Tree for built in menu */
    struct wlr_scene_tree *m_menu_tree;

    std::list<Workspace*> m_workspaces;
    Workspace *m_workspace_current = nullptr;
    Workspace *m_workspace_last = nullptr;

    struct wlr_foreign_toplevel_manager_v1 *m_foreign_toplevel_manager;

    std::list<View*> m_views;
#if 0
    struct wlr_scene_node *layers[NUM_LAYERS];
    struct wlr_renderer *drw;
    struct wlr_allocator *alloc;

    Xdg_shell *xdg_shell;
    Xdg_decoration *xdg_decoration;

    struct wlr_xdg_activation_v1 *activation;
    struct wl_listener request_activate;

    struct wlr_output_layout *output_layout;

    struct wl_list mons;
    Monitor *selmon;
#endif
    // struct wl_listener new_output;
    // wl_listener_wrapper m_new_output;

#if 0
//    struct wl_list clients; /* tiling order */
//    struct wl_list fstack;  /* focus order */

    struct wlr_idle *idle;
    struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr;
    struct wl_listener idle_inhibitor_create;

    struct wlr_layer_shell_v1 *layer_shell;
    struct wl_listener new_layer_shell_surface;

    struct wlr_input_inhibit_manager *input_inhibit_mgr;

    Cursor *cursor;
    KeyboardManager *keyboard_manager;


    Seat *seat;

    struct wl_listener output_mgr_apply;
    struct wl_listener output_mgr_test;

    struct wlr_input_method_manager_v2 *input_method_manager;
    struct wlr_text_input_manager_v3 *text_input_manager;
    struct input_method_relay *input_relay;

    unsigned int cursor_mode;
    Client *grabc;
    int grabcx;
    int grabcy;
#endif
};

